﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using ToracTechnologies.ExpressionTreeLibrary.DynamicApi.Framework;

namespace ToracTechnologies.ExpressionTreeLibrary.DynamicApi
{

    /// <summary>
    /// Provides Entity Framework Dynamic Utility Methods
    /// </summary>
    public static class EntityFrameworkUtilities
    {

        #region Public Static Methods

        #region Paginate And Order By

        /// <summary>
        /// Takes The IQueryable And Grabs The Current Page We Are On
        /// </summary>
        /// <typeparam name="T">Type Of Records That Are Returned</typeparam>
        /// <param name="QueryToModify">Query To Modify</param>
        /// <param name="CurrentPageNumber">What Page Number Are You Currently On</param>
        /// <param name="HowManyRecordsPerPage">How Many Records Per Page</param>
        /// <returns>IQueryable Of T</returns>
        public static IQueryable<T> PaginateResults<T>(IQueryable<T> QueryToModify, int CurrentPageNumber, int HowManyRecordsPerPage)
        {
            //how to call this
            //sql = sql.OrderBy(sidx, isAscending).PaginateResults(page, rows);

            //run a quick check to make sure the page number is ok
            if (CurrentPageNumber == 0)
                throw new IndexOutOfRangeException("Current Page Number Can't Be 0. Use 1 For The First Page");

            //go skip however many pages we are past...and take only x amount of records per page
            return QueryToModify.Skip((CurrentPageNumber - 1) * HowManyRecordsPerPage).Take(HowManyRecordsPerPage).AsQueryable();
        }

        /// <summary>
        /// Modifies Queryable Of T And Orders It For Entity Framework.
        /// </summary>
        /// <typeparam name="T">Type Of The IQueryable</typeparam>
        /// <param name="QueryToModify">Query To Modifiy</param>
        /// <param name="PropertyNameToCheck">Property Name To Sort By</param>
        /// <param name="OrderByAscending">Sort By Ascending or Descending</param>
        /// <returns>IOrderedQueryable Of T</returns>
        public static IOrderedQueryable<T> OrderBy<T>(IQueryable<T> QueryToModify, string PropertyNameToCheck, bool OrderByAscending)
        {
            //The problem with the entity framework sort by is that the key (the field to be sorted on could be anything). You can't say expression|func|tablename,object|| because 
            //it can't resolve the object into whatever the key should be. So we need to do it this way. I would have liked to returned just the Linq Expression

            //example of how to call this
            //sql = sql.OrderBy("CreatedDate", true);

            //Holds the property info based on the property name passed in
            PropertyInfo thisPropertyInfo = typeof(T).GetProperty(PropertyNameToCheck);

            //Create the parameter which will go into the lambda
            ParameterExpression Arg = Expression.Parameter(typeof(T), "myParameter");

            //grab the member access so we can get the value of the property at run time
            MemberExpression ValueAtRuntime = Expression.MakeMemberAccess(Arg, thisPropertyInfo);

            // use reflection to get and call your own generic method that composes
            // the orderby into the query.
            MethodInfo Method = typeof(EntityFrameworkUtilities).GetMethod("OrderByProperty", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(typeof(T), thisPropertyInfo.PropertyType);

            //go invoke the order by property generically
            return (IOrderedQueryable<T>)Method.Invoke(null, new object[] { QueryToModify, Arg, ValueAtRuntime, OrderByAscending });
        }

        #endregion

        #region String Contains

        /// <summary>
        /// Builds a dynamic expression which test if the property name contains the ContainTest string Passed in
        /// </summary>
        /// <typeparam name="T">Type Of The Object Passed In (Object Container Of PropertyNameToCheck)</typeparam>
        /// <param name="PropertyNameToCheck">Property Name To Check</param>
        /// <param name="ContainsTest">What To Check For In The Property</param>
        /// <returns>Linq Expression</returns>
        public static Expression<Func<T, bool>> StringContains<T>(string PropertyNameToCheck, string ContainsTest)
        {
            //**please see the LinqUtilities for specific examples
            //use the linq utilities...linq to objects can take Expressions.. but EF can't take funcs...
            //we pass in false for case sensitive because sql isn't case sensitive
            return ExpressionBuilder.StringContains<T>(PropertyNameToCheck, ContainsTest, false, false);
        }

        #endregion

        #endregion

        #region Private Helper Methods

        /// <summary>
        /// Builds the sort order expression into the IQueryable and returns the IQueryable
        /// </summary>
        /// <typeparam name="T">Type Of The IQueryable</typeparam>
        /// <typeparam name="TKey">Type Of The Key Which Will Be Sorted. Is That Field An Int? Decimal? Date?</typeparam>
        /// <param name="QueryToModify">IQueryable</param>
        /// <param name="Arg">Parameter Expression</param>
        /// <param name="ValueAtRunTime">Expression To Grab The Property</param>
        /// <param name="OrderByAscending">Order By Ascending Or Descending Order</param>
        /// <returns></returns>
        private static IOrderedQueryable<T> OrderByProperty<T, TKey>(this IQueryable<T> QueryToModify, ParameterExpression Arg, Expression ValueAtRunTime, bool OrderByAscending)
        {
            //**** this method gets invoked dynamically from OrderBy. So it is being used! 

            //create the typed lambda
            var SortOrderExpression = Expression.Lambda<Func<T, TKey>>(ValueAtRunTime, Arg);

            //based on the way we want to sort go add the expression and return the query
            if (OrderByAscending)
            {
                //order by ascending
                return QueryToModify.OrderBy(SortOrderExpression);
            }
            else
            {
                //order by descending
                return QueryToModify.OrderByDescending(SortOrderExpression);
            }
        }

        #endregion
    }

}
